package endecode

import (
	"crypto"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
	"reflect"

	"golang.org/x/crypto/bn256"
)

const (
	PublicKeySize  = 32 // in bytes
	PrivateKeySize = 64
	SignatureSize  = 64
	SeedSize       = 32
)

type PublicKey []byte  // Ed25519 public keys
type PrivateKey []byte // Ed25519 private keys implements crypto.Signer

func (priv PrivateKey) Public() crypto.PublicKey {
	publicKey := make([]byte, PublicKeySize)
	copy(publicKey, priv[32:])
	return PublicKey(publicKey)
}
func (priv PrivateKey) Sign(message []byte) (signature []byte, err error) {
	sk := big.NewInt(0).SetBytes(priv)
	sig := Sign(sk, string(message))

	return sig.Marshal(), nil
}

func (pub PublicKey) Verify(message, sigBytes []byte) bool {
	sig, _ := (*bn256.G1).Unmarshal(new(bn256.G1).ScalarBaseMult(big.NewInt(1)), sigBytes)
	pk, _ := (*bn256.G2).Unmarshal(new(bn256.G2).ScalarBaseMult(big.NewInt(1)), pub)

	return Verify(pk, string(message), sig)
}

func KeyGenerate() (*big.Int, *bn256.G2, PrivateKey, PublicKey) {
	sk, pk, _ := bn256.RandomG2(rand.Reader)
	skBytes := sk.Bytes()
	pkBytes := pk.Marshal()
	return sk, pk, skBytes, pkBytes
}
func Sign(sk *big.Int, msg string) *bn256.G1 {
	h := hashToG1(msg)
	sig := new(bn256.G1).ScalarMult(h, sk)
	return sig
}
func Verify(pk *bn256.G2, msg string, sig *bn256.G1) bool {
	g2 := new(bn256.G2).ScalarBaseMult(big.NewInt(1))
	h := hashToG1(msg)
	lp := bn256.Pair(sig, g2)
	rp := bn256.Pair(h, pk)
	ok := reflect.DeepEqual(lp.Marshal(), rp.Marshal())
	return ok
}
func Aggregate(sigs []*bn256.G1) *bn256.G1 {
	if len(sigs) <= 1 {
		fmt.Printf("invalid input.")
		return nil
	}

	asig := new(bn256.G1).ScalarBaseMult(big.NewInt(0))
	for _, sig := range sigs {
		asig.Add(asig, sig)
	}

	return asig
}
func AVerify(asig *bn256.G1, msgs []string, pks []*bn256.G2) bool {
	if len(msgs) != len(pks) {
		fmt.Printf("messages and public keys have different quantity.")
		return false
	}
	g2 := new(bn256.G2).ScalarBaseMult(big.NewInt(1))

	hs := make([]*bn256.G1, len(msgs), len(msgs))

	for i, msg := range msgs {
		hs[i] = hashToG1(msg)
	}

	lp := bn256.Pair(asig, g2)

	rp := bn256.Pair(hs[0], pks[0])

	for i := 1; i < len(pks); i++ {
		rp.Add(rp, bn256.Pair(hs[i], pks[i]))
	}
	ok := reflect.DeepEqual(lp.Marshal(), rp.Marshal())
	return ok
}

func hashToG1(msg string) *bn256.G1 {
	H := sha256.New()
	H.Write([]byte(msg))
	tmp := new(big.Int).SetBytes(H.Sum(nil))
	return new(bn256.G1).ScalarBaseMult(tmp)
}
